<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HanLP在线演示</title>
    <style>
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f9f9f9;
        }
        
        h1, h2 {
            color: #1e88e5;
        }
        
        .container {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
        }
        
        .input-panel {
            flex: 1;
            min-width: 300px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        .output-panel {
            flex: 2;
            min-width: 400px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        textarea {
            width: 100%;
            height: 150px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
            resize: vertical;
        }
        
        button {
            background-color: #1e88e5;
            color: white;
            border: none;
            padding: 10px 15px;
            margin: 10px 0;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s;
        }
        
        button:hover {
            background-color: #1565c0;
        }
        
        .btn-group {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }
        
        .result {
            margin-top: 20px;
            background-color: #f5f5f5;
            padding: 15px;
            border-radius: 4px;
            overflow-x: auto;
        }
        
        .loading {
            display: none;
            text-align: center;
            margin: 20px 0;
        }
        
        .loading::after {
            content: "处理中...";
            display: inline-block;
            animation: dotAnimation 1.5s infinite;
        }
        
        @keyframes dotAnimation {
            0% { content: "处理中.  "; }
            33% { content: "处理中.. "; }
            66% { content: "处理中..."; }
        }
        
        .server-url {
            margin-bottom: 15px;
        }
        
        .server-url input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        
        .example-text {
            margin-top: 10px;
            font-size: 0.9em;
            color: #666;
        }
        
        .entity {
            display: inline-block;
            margin: 0 2px;
            padding: 0 3px;
            border-radius: 3px;
        }
        
        .entity-PER { background-color: #ffcdd2; }
        .entity-LOC { background-color: #bbdefb; }
        .entity-ORG { background-color: #c8e6c9; }
        .entity-TIME { background-color: #fff9c4; }
        .entity-MISC { background-color: #e1bee7; }
        
        .tab {
            overflow: hidden;
            border: 1px solid #ccc;
            background-color: #f1f1f1;
            border-radius: 4px 4px 0 0;
        }
        
        .tab button {
            background-color: inherit;
            float: left;
            border: none;
            outline: none;
            cursor: pointer;
            padding: 14px 16px;
            transition: 0.3s;
            color: #333;
        }
        
        .tab button:hover {
            background-color: #ddd;
        }
        
        .tab button.active {
            background-color: #1e88e5;
            color: white;
        }
        
        .tabcontent {
            display: none;
            padding: 15px;
            border: 1px solid #ccc;
            border-top: none;
            border-radius: 0 0 4px 4px;
            animation: fadeEffect 1s;
        }
        
        @keyframes fadeEffect {
            from {opacity: 0;}
            to {opacity: 1;}
        }
    </style>
</head>
<body>
    <h1>HanLP在线演示</h1>
    <p>基于HanLP的中文自然语言处理在线演示工具</p>
    
    <div class="container">
        <div class="input-panel">
            <h2>输入</h2>
            
            <div class="server-url">
                <label for="serverUrl">服务器地址：</label>
                <input type="text" id="serverUrl" value="http://localhost:8000" />
            </div>
            
            <textarea id="inputText" placeholder="请输入要分析的中文文本...">上海华瑞银行与中国人民银行达成战略合作，将在2021年3月开展金融科技创新。</textarea>
            
            <div class="example-text">
                <button id="useExample">使用示例文本</button>
                <p>示例：上海华瑞银行与中国人民银行达成战略合作，将在2021年3月开展金融科技创新。</p>
            </div>
            
            <div class="btn-group">
                <button id="btnTokenize">分词</button>
                <button id="btnPOS">词性标注</button>
                <button id="btnNER">命名实体识别</button>
                <button id="btnDep">依存句法分析</button>
                <button id="btnMTL">多任务处理</button>
            </div>
        </div>
        
        <div class="output-panel">
            <h2>结果</h2>
            
            <div class="tab">
                <button class="tablinks active" onclick="openTab(event, 'rawOutput')">原始输出</button>
                <button class="tablinks" onclick="openTab(event, 'visualOutput')">可视化</button>
            </div>
            
            <div id="rawOutput" class="tabcontent" style="display: block;">
                <div id="loading" class="loading"></div>
                <pre id="result" class="result">结果将在这里显示...</pre>
            </div>
            
            <div id="visualOutput" class="tabcontent">
                <div id="visualResult" class="result">选择一种分析方式查看可视化结果...</div>
            </div>
        </div>
    </div>
    
    <script>
        // 服务器API映射
        const API_ENDPOINTS = {
            tokenize: '/api/tokenize',
            pos: '/api/pos',
            ner: '/api/ner',
            dep: '/api/dep',
            mtl: '/api/mtl'
        };
        
        // 示例文本
        const EXAMPLE_TEXT = "上海华瑞银行与中国人民银行达成战略合作，将在2021年3月开展金融科技创新。";
        
        // 当前处理类型
        let currentProcessType = '';
        
        // DOM 元素
        const serverUrlInput = document.getElementById('serverUrl');
        const inputTextArea = document.getElementById('inputText');
        const resultArea = document.getElementById('result');
        const visualResultArea = document.getElementById('visualResult');
        const loadingElem = document.getElementById('loading');
        
        // 按钮点击事件
        document.getElementById('btnTokenize').addEventListener('click', () => processText('tokenize'));
        document.getElementById('btnPOS').addEventListener('click', () => processText('pos'));
        document.getElementById('btnNER').addEventListener('click', () => processText('ner'));
        document.getElementById('btnDep').addEventListener('click', () => processText('dep'));
        document.getElementById('btnMTL').addEventListener('click', () => processText('mtl'));
        
        // 使用示例文本
        document.getElementById('useExample').addEventListener('click', () => {
            inputTextArea.value = EXAMPLE_TEXT;
        });
        
        // 处理文本函数
        async function processText(processType) {
            const serverUrl = serverUrlInput.value.trim();
            const text = inputTextArea.value.trim();
            
            if (!text) {
                alert('请输入要分析的文本');
                return;
            }
            
            if (!serverUrl) {
                alert('请输入服务器地址');
                return;
            }
            
            currentProcessType = processType;
            
            // 显示加载提示
            loadingElem.style.display = 'block';
            resultArea.innerText = '正在处理...';
            visualResultArea.innerText = '正在处理...';
            
            try {
                const response = await fetch(`${serverUrl}${API_ENDPOINTS[processType]}`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ text: text })
                });
                
                const data = await response.json();
                
                // 更新结果
                resultArea.innerText = JSON.stringify(data, null, 2);
                
                // 更新可视化结果
                updateVisualResult(data, processType);
                
            } catch (error) {
                resultArea.innerText = `错误: ${error.message}`;
                visualResultArea.innerText = `处理出错: ${error.message}`;
            } finally {
                loadingElem.style.display = 'none';
            }
        }
        
        // 更新可视化结果
        function updateVisualResult(data, processType) {
            if (!data || data.status !== 'ok') {
                visualResultArea.innerHTML = `<p>处理失败: ${data?.message || '未知错误'}</p>`;
                return;
            }
            
            let html = '';
            
            switch (processType) {
                case 'tokenize':
                    html = visualizeTokens(data.result);
                    break;
                case 'pos':
                    html = visualizePOS(data.tokens, data.pos);
                    break;
                case 'ner':
                    html = visualizeNER(data.result);
                    break;
                case 'dep':
                    html = visualizeDependencies(data.tokens, data.dependencies);
                    break;
                case 'mtl':
                    html = visualizeMTL(data.result);
                    break;
                default:
                    html = '<p>暂不支持此类型的可视化</p>';
            }
            
            visualResultArea.innerHTML = html;
        }
        
        // 分词可视化
        function visualizeTokens(tokens) {
            if (!tokens || !tokens.length) return '<p>无分词结果</p>';
            
            return `
                <h3>分词结果</h3>
                <div style="line-height: 2.5;">
                    ${tokens.map(token => 
                        `<span style="display: inline-block; margin: 0 2px; padding: 2px 4px; border: 1px solid #ddd; border-radius: 4px;">${token}</span>`
                    ).join('')}
                </div>
                <p>共 ${tokens.length} 个词</p>
            `;
        }
        
        // 词性标注可视化
        function visualizePOS(tokens, posTags) {
            if (!tokens || !tokens.length || !posTags || !posTags.length) {
                return '<p>无词性标注结果</p>';
            }
            
            const posColors = {
                'n': '#bbdefb', // 名词
                'v': '#c8e6c9', // 动词
                'a': '#ffcdd2', // 形容词
                'd': '#fff9c4', // 副词
                'p': '#e1bee7', // 介词
                'r': '#b2dfdb', // 代词
                'm': '#ffccbc', // 数词
                'q': '#d1c4e9', // 量词
                'c': '#b3e5fc', // 连词
                'u': '#f0f4c3', // 助词
                'w': '#cfd8dc', // 标点
                'default': '#f5f5f5' // 默认颜色
            };
            
            // 判断词性的首字母来确定颜色
            function getPosColor(pos) {
                const firstChar = pos.charAt(0).toLowerCase();
                return posColors[firstChar] || posColors['default'];
            }
            
            return `
                <h3>词性标注结果</h3>
                <div style="line-height: 2.5;">
                    ${tokens.map((token, index) => 
                        `<div style="display: inline-block; text-align: center; margin: 0 2px;">
                            <span style="display: inline-block; padding: 2px 4px; border: 1px solid #ddd; border-radius: 4px; background-color: ${getPosColor(posTags[index])};">${token}</span>
                            <span style="display: block; font-size: 12px; color: #666;">${posTags[index]}</span>
                        </div>`
                    ).join('')}
                </div>
            `;
        }
        
        // 命名实体识别可视化
        function visualizeNER(nerResult) {
            if (!nerResult || !nerResult.length) {
                return '<p>未找到命名实体</p>';
            }
            
            const entityColors = {
                'PER': '#ffcdd2',
                'LOC': '#bbdefb',
                'ORG': '#c8e6c9',
                'TIME': '#fff9c4',
                'MISC': '#e1bee7',
                'default': '#f5f5f5'
            };
            
            let html = '<h3>命名实体识别结果</h3><div>';
            
            // 提取所有实体
            let entities = [];
            for (let i = 0; i < nerResult.length; i++) {
                const words = nerResult[i];
                const tags = nerResult[i+1];
                
                if (Array.isArray(words) && Array.isArray(tags)) {
                    for (let j = 0; j < words.length; j++) {
                        entities.push({
                            word: words[j],
                            tag: tags[j]
                        });
                    }
                    i++; // 跳过标签数组
                }
            }
            
            // 如果无法解析为上面的格式，尝试备选格式
            if (entities.length === 0 && typeof nerResult === 'object') {
                // 可能是不同的结果格式，尝试适配
                html += '<p>实体列表:</p><ul>';
                for (const key in nerResult) {
                    if (Array.isArray(nerResult[key])) {
                        html += `<li><strong>${key}</strong>: ${nerResult[key].join(', ')}</li>`;
                    }
                }
                html += '</ul>';
                return html;
            }
            
            // 构建可视化表示
            if (entities.length > 0) {
                html += '<div style="line-height: 2.5;">';
                entities.forEach(entity => {
                    const tagType = entity.tag.split('-')[entity.tag.includes('-') ? 1 : 0];
                    const color = entityColors[tagType] || entityColors['default'];
                    html += `
                        <span class="entity" style="background-color: ${color};" title="${entity.tag}">
                            ${entity.word}
                            <small style="font-size: 0.8em;">[${entity.tag}]</small>
                        </span>
                    `;
                });
                html += '</div>';
            } else {
                html += '<p>原始结果:</p><pre>' + JSON.stringify(nerResult, null, 2) + '</pre>';
            }
            
            html += '</div>';
            return html;
        }
        
        // 依存句法分析可视化
        function visualizeDependencies(tokens, dependencies) {
            if (!tokens || !tokens.length || !dependencies) {
                return '<p>无依存句法分析结果</p>';
            }
            
            let html = '<h3>依存句法分析结果</h3>';
            
            // 表格形式展示
            html += `
                <table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
                    <thead>
                        <tr>
                            <th style="border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2;">序号</th>
                            <th style="border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2;">词语</th>
                            <th style="border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2;">依存关系</th>
                            <th style="border: 1px solid #ddd; padding: 8px; background-color: #f2f2f2;">指向词序号</th>
                        </tr>
                    </thead>
                    <tbody>
            `;
            
            // 判断依存关系结果的格式，并进行适配
            if (Array.isArray(dependencies) && tokens.length === dependencies.length) {
                // 这是期望的标准格式
                for (let i = 0; i < tokens.length; i++) {
                    const headIndex = dependencies[i][0];
                    const relation = dependencies[i][1];
                    
                    html += `
                        <tr>
                            <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${i + 1}</td>
                            <td style="border: 1px solid #ddd; padding: 8px;">${tokens[i]}</td>
                            <td style="border: 1px solid #ddd; padding: 8px;">${relation}</td>
                            <td style="border: 1px solid #ddd; padding: 8px; text-align: center;">${headIndex}</td>
                        </tr>
                    `;
                }
            } else {
                // 如果格式不是预期的，显示原始JSON
                html += `
                    <tr>
                        <td colspan="4" style="border: 1px solid #ddd; padding: 8px;">
                            <pre>${JSON.stringify(dependencies, null, 2)}</pre>
                        </td>
                    </tr>
                `;
            }
            
            html += '</tbody></table>';
            return html;
        }
        
        // 多任务处理可视化
        function visualizeMTL(result) {
            if (!result) {
                return '<p>无多任务处理结果</p>';
            }
            
            let html = '<h3>多任务处理结果</h3>';
            
            // 对每种任务分别可视化
            if (result.tok) {
                html += '<h4>分词结果</h4>';
                html += visualizeTokens(result.tok);
            }
            
            if (result.pos) {
                html += '<h4>词性标注结果</h4>';
                html += visualizePOS(result.tok, result.pos);
            }
            
            if (result.ner) {
                html += '<h4>命名实体识别结果</h4>';
                html += `<pre>${JSON.stringify(result.ner, null, 2)}</pre>`;
            }
            
            if (result.dep) {
                html += '<h4>依存句法分析结果</h4>';
                html += visualizeDependencies(result.tok, result.dep);
            }
            
            // 如果没有上述任何一种结果，显示原始JSON
            if (!result.tok && !result.pos && !result.ner && !result.dep) {
                html += `<pre>${JSON.stringify(result, null, 2)}</pre>`;
            }
            
            return html;
        }
        
        // Tab切换功能
        function openTab(evt, tabName) {
            const tabcontent = document.getElementsByClassName("tabcontent");
            for (let i = 0; i < tabcontent.length; i++) {
                tabcontent[i].style.display = "none";
            }
            
            const tablinks = document.getElementsByClassName("tablinks");
            for (let i = 0; i < tablinks.length; i++) {
                tablinks[i].className = tablinks[i].className.replace(" active", "");
            }
            
            document.getElementById(tabName).style.display = "block";
            evt.currentTarget.className += " active";
        }
    </script>
</body>
</html> 